
//#include <config.h>
#include <types.h>
#include <graph.h>

#include "GVGraph.h"

static inline Agsym_t *_agedgeattr(Agraph_t *g, const QString& name, const QString& value)
{
	return agedgeattr(g, const_cast<char *>(qPrintable(name)), const_cast<char *>(qPrintable(value)));
}

//static inline Agsym_t* _agnodeattr(Agraph_t *g, const QString& name, const QString& value)
//{
//	return agattr(g, AGNODE, "label", NODENAME_ESC);
//return 0;
////	return agnodeattr(g, const_cast<char *>(qPrintable(name)), const_cast<char *>(qPrintable(value)));
//}

/// The agopen method for opening a graph
static inline Agraph_t* _agopen(const QString& name, int kind)
{
	return agopen(const_cast<char *>(qPrintable(name)), kind);
}

static inline int _gvLayout(GVC_t *gvc, graph_t *g, const QString& engine)
{
	return gvLayout(gvc, g, const_cast<char *>(qPrintable(engine)));
}

/// Add an alternative value parameter to the method for getting an object's attribute
static inline QString _agget(void *object, const QString& attr,
										const QString& alt = "")
{
	const QString& str = agget(object, const_cast<char *>(qPrintable(attr)));
	return str.isEmpty() ? alt : str;
}

/// Directly use agsafeset which always works, contrarily to agset
static inline int _agset(void *object, const QString& attr, const QString& value)
{
	return agsafeset(object, const_cast<char *>(qPrintable(attr)),
		const_cast<char *>(qPrintable(value)),
		const_cast<char *>(qPrintable(value)));
}

static inline Agnode_t* _agnode(Agraph_t *g, const QString& name)
{
	return agnode(g, const_cast<char *>(qPrintable(name)));
}


GVGraph::GVGraph(const QString& name, double node_size) :
		_context(gvContext()),
		_graph(_agopen(name, AGDIGRAPHSTRICT)) // Strict directed graph, see libgraph doc
{
	// Set graph attributes
	_agset(_graph, "dpi", "96,0");
	_agset(_graph, "nodesep", "1,0");
	_agset(_graph, "ranksep", "1,0");
	_agset(_graph, "outputMode", "nodesfirst");

	// Set default attributes for the future nodes
	//_agnodeattr(_graph, "regular", "true");
}

GVGraph::~GVGraph()
{
	gvFreeLayout(_context, _graph);
	agclose(_graph);
	gvFreeContext(_context);
}

QString toGVString(double val)
{
	QString str = QString("%1").arg(val).replace(".", ",");
	return str;
}

void GVGraph::addNode(const QString& name, int width, int height)
{
	removeNode(name);
	
	Agnode_t* gvNode = _agnode(_graph, name);
	_agset(gvNode, "width",	qPrintable(toGVString(width / 96.0)));
	_agset(gvNode, "height", qPrintable(toGVString(height/ 96.0)));
	_agset(gvNode, "constraint", "false");
	_nodes[name] = gvNode;
}

void GVGraph::removeNode(const QString& name)
{
	if(_nodes.find(name) != _nodes.end())
	{
		agdelete(_graph, _nodes[name]);
		_nodes.erase(name);

		for (EdgesVector::iterator i = _edges.begin(); i != _edges.end(); ++i)
		{
			const std::pair<QString, QString>& key = i->first;
			if (key.first == name || key.second == name)
				removeEdge(key);
		}
	}
}

void GVGraph::clearNodes()
{
	for (std::map<QString, Agnode_t*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i)
	{
		removeNode(i->first);
	}
}
	
bool GVGraph::containsNode(const QString& name)
{
	return _nodes.find(name) != _nodes.end();
}

bool GVGraph::setRootNode(const QString& name)
{
	if(_nodes.find(name) == _nodes.end())
		return false;
	_agset(_graph, "root", name);
	return true;
}

void GVGraph::addEdge(const QString& source, const QString& target)
{
	if(containsNode(source) && containsNode(target))
	{
		std::pair<const QString&, const QString&> key(source, target);
		if(!containsEdge(key))
			_edges[key] = agedge(_graph, _nodes[source], _nodes[target]);
	}
}

void GVGraph::removeEdge(const QString& source, const QString& target)
{
	removeEdge(std::pair<const QString&, const QString&>(source, target));
}

void GVGraph::removeEdge(const std::pair<const QString&, const QString&>& key)
{
	if(containsEdge(key))
	{
	 //   agdelete(_graph, _edges[key]->first);
		agdelete(_graph, _edges[key]);
		_edges.erase(key);
	}
}

bool GVGraph::containsEdge(const QString& source, const QString& target)
{
	return containsEdge(std::pair<const QString&, const QString&>(source, target));
}

bool GVGraph::containsEdge(const std::pair<const QString&, const QString&>& key)
{
	return _edges.find(key) != _edges.end();
}
//
//void GVGraph::setFont(QFont font)
//{
//	_font=font;
//
//	_agset(_graph, "fontname", font.family());
//	_agset(_graph, "fontsize", const QString&("%1").arg(font.pointSizeF()));
//
//	_agnodeattr(_graph, "fontname", font.family());
//	_agnodeattr(_graph, "fontsize", const QString&("%1").arg(font.pointSizeF()));
//
//	_agedgeattr(_graph, "fontname", font.family());
//	_agedgeattr(_graph, "fontsize", const QString&("%1").arg(font.pointSizeF()));
//}

void GVGraph::applyLayout()
{
	gvFreeLayout(_context, _graph);
	_gvLayout(_context, _graph, "sfdp");
}
//
//QRectF GVGraph::boundingRect() const
//{
//	double dpi = boost::lexical_cast<double>(_agget(_graph, "dpi", "96,0"));
//	return QRectF(_graph->u.bb.LL.x*(dpi/DotDefaultDPI), _graph->u.bb.LL.y*(dpi/DotDefaultDPI),
//				  _graph->u.bb.UR.x*(dpi/DotDefaultDPI), _graph->u.bb.UR.y*(dpi/DotDefaultDPI));
//}

std::vector<GVNode> GVGraph::nodes() const 
{

	std::vector<GVNode> list; 
	double dpi = getDpi();
	for(NodesVector::const_iterator it=_nodes.begin(); it!=_nodes.end();++it)
	{
		Agnode_t *node=it->second;
		GVNode object;

		//Set the name of the node
		object.name=node->name;

		//Fetch the X coordinate, apply the DPI conversion rate (actual DPI / 72, used by dot)
		double x=node->u.coord.x*(dpi/DotDefaultDPI);

		//Translate the Y coordinate from bottom-left to top-left corner
		double y=(_graph->u.bb.UR.y - node->u.coord.y)*(dpi/DotDefaultDPI);
		object.x = x  ;
		object.y = y ;

		//Transform the width and height from inches to pixels
		object.height=node->u.height*dpi;
		object.width=node->u.width*dpi;

		list.push_back(object);
	}

	return list;
}

double GVGraph::getDpi() const
{
	QString dpiStr = _agget(_graph, "dpi", "96,0");
	double dpi = dpiStr.replace(",", ".").toDouble();
	return dpi;
}

std::vector<GVEdge> GVGraph::edges() const
{
	double dpi = getDpi();
	std::vector<GVEdge> list;
	for(EdgesVector::const_iterator it =_edges.begin(); it != _edges.end(); ++it)
	{
		Agedge_t *edge = it->second;
		GVEdge object;

		//Fill the source and target node names
		object.source=edge->tail->name;
		object.target=edge->head->name;

		////Calculate the path from the spline (only one spline, as the graph is strict. If it
		////wasn't, we would have to iterate over the first list too)
		////Calculate the path from the spline (only one as the graph is strict)
		//if((edge->u.spl->list!=0) && (edge->u.spl->list->size%3 == 1))
		//{
		//	//If there is a starting point, draw a line from it to the first curve point
		//	if(edge->u.spl->list->sflag)
		//	{
		//		object.path.moveTo(edge->u.spl->list->sp.x*(dpi/DotDefaultDPI),
		//						(_graph->u.bb.UR.y - edge->u.spl->list->sp.y)*(dpi/DotDefaultDPI));
		//		object.path.lineTo(edge->u.spl->list->list[0].x*(dpi/DotDefaultDPI),
		//				(_graph->u.bb.UR.y - edge->u.spl->list->list[0].y)*(dpi/DotDefaultDPI));
		//	}
		//	else
		//		object.path.moveTo(edge->u.spl->list->list[0].x*(dpi/DotDefaultDPI),
		//				(_graph->u.bb.UR.y - edge->u.spl->list->list[0].y)*(dpi/DotDefaultDPI));

		//	//Loop over the curve points
		//	for(int i=1; i<edge->u.spl->list->size; i+=3)
		//		object.path.cubicTo(edge->u.spl->list->list[i].x*(dpi/DotDefaultDPI), 
		//				(_graph->u.bb.UR.y - edge->u.spl->list->list[i].y)*(dpi/DotDefaultDPI),
		//				edge->u.spl->list->list[i+1].x*(dpi/DotDefaultDPI),
		//				(_graph->u.bb.UR.y - edge->u.spl->list->list[i+1].y)*(dpi/DotDefaultDPI),
		//				edge->u.spl->list->list[i+2].x*(dpi/DotDefaultDPI),
		//				(_graph->u.bb.UR.y - edge->u.spl->list->list[i+2].y)*(dpi/DotDefaultDPI));

		//	//If there is an ending point, draw a line to it
		//	if(edge->u.spl->list->eflag)
		//		object.path.lineTo(edge->u.spl->list->ep.x*(dpi/DotDefaultDPI),
		//						(_graph->u.bb.UR.y - edge->u.spl->list->ep.y)*(dpi/DotDefaultDPI));
		//}

		list.push_back(object);
	}

	return list;
}

QRectF GVGraph::boundingRect() const
{
	qreal dpi = _agget(_graph, QString("dpi"), QString("96,0")).toDouble();
	return QRectF(_graph->u.bb.LL.x*(dpi/DotDefaultDPI), _graph->u.bb.LL.y*(dpi/DotDefaultDPI),
				  _graph->u.bb.UR.x*(dpi/DotDefaultDPI), _graph->u.bb.UR.y*(dpi/DotDefaultDPI));
}
